#include "contour.h"
#include "point.h"
#include "sstream"
#include "line.h"
#include "cell.h"
#include "grid.h"
#include <algorithm>
#include <numeric>
#include <map>
#include <vector>
#include <exception>

contour_t::contour_t()
{
}

void contour_t::add(point_t const & point)
{
    if (vertices_.size() == 0 || point.x < left_point_.x)
    {
        left_point_ = point;
    }
    vertices_.push_back(point);
}

bool contour_t::empty() const
{
    return (vertices_.size() == 0);
}

size_t contour_t::size() const
{
    return vertices_.size();
}

point_t const & contour_t::operator[] (size_t index) const
{
    return vertices_.at(index);
}

void contour_t::direction(direction_t new_direction)
{
    if(direction() != new_direction)
    {
        std::reverse(vertices_.begin(), vertices_.end());
    }
}

direction_t contour_t::direction() const
{
    point_t current = vertices_[0];
    size_t top_idx = 0;
    for(size_t i = 0; i < vertices_.size(); ++i)
    {
        if(current.y < vertices_[i].y)
        {
            current = vertices_[i];
            top_idx = i;
        }
    }
    point_t next = (top_idx == (vertices_.size() - 1)) ? vertices_[0] : vertices_[top_idx + 1];
    point_t prev = (top_idx == 0) ? vertices_[vertices_.size() - 1] : vertices_[top_idx - 1];
    point_t a = current - prev;
    point_t b = next - current;
    if ((a.x * b.y - a.y * b.x) > 0)
    {
        return CLOCKWISE;
    }
    return COUNTERCLOCKWISE;
}

point_t const & contour_t::left_point() const
{
    return left_point_;
}

std::istream & operator>>(std::istream & in, contour_t & contour)
{
    contour_data_t contour_data;
    in >> contour_data;
    contour = get_contour(contour_data);
    return in;
}

std::ostream & operator<<(std::ostream & out, contour_t const & ring)
{
    for (size_t i = 0; i != ring.vertices_.size(); ++i)
    {
        out << ring.vertices_[i] << " ";
    }
    out << std::endl;
    return out;
}


/// contour_data_t

point_t * find(point_t const & point, contour_data_t & vertices)
{
    for (size_t i = 0; i < vertices.size(); ++i)
    {
        if (point == vertices[i])
        {
            return & vertices[i];
        }

    }
    return 0;
}

point_t top_left_corner(contour_data_t const & vertices)
{
    if(vertices.size() == 0)
        return point_t(0, 0);
    double min_x = vertices[0].x;
    double min_y = vertices[0].y;
    std::vector<point_t>::const_iterator i = vertices.begin() + 1;
    for(; i != vertices.end(); ++i)
    {
        if(i->x < min_x)
        {
            min_x = i->x;
        }
        if(i->y < min_y)
        {
            min_y = i->y;
        }
    }
    return point_t(min_x, min_y);
}

point_t bottom_right_corner(contour_data_t const & vertices)
{
    if(vertices.size() == 0)
        return point_t(0, 0);
    double max_x = vertices[0].x;
    double max_y = vertices[0].y;
    std::vector<point_t>::const_iterator i = vertices.begin() + 1;
    for(; i != vertices.end(); ++i)
    {
        if(i->x > max_x)
        {
            max_x = i->x;
        }
        if(i->y > max_y)
        {
            max_y = i->y;
        }
    }
    return point_t(max_x, max_y);
}

contour_t get_default_contour(contour_data_t const & vertices)
{
    contour_t contour;
    contour.add(top_left_corner(vertices));
    contour.add(bottom_right_corner(vertices));
    contour.add(*vertices.begin());
    return contour;
}

contour_t get_contour(contour_data_t const & vertices)
{
    contour_t contour(vertices.begin(), vertices.end());

    int diameter = abs(top_left_corner(vertices).x - bottom_right_corner(vertices).x);
    grid_t grid(diameter / 10);
    inserter_without_intersections f;
    try
    {
        grid.visit(contour, f);
    }
    catch(std::exception exc)
    {
        std::cout << exc.what() << std::endl;
        return get_default_contour(vertices);
    }

    return contour;
}

std::istream & operator>>(std::istream & in, contour_data_t & vertices)
{
    vertices.clear();
    std::stringstream ss;
    std::string buff;
    std::getline(in, buff);
    ss << buff;
    while (!ss.eof())
    {
        point_t point;
        ss >> point;
        if (!ss.eof()) vertices.push_back(point);
    }
    return in;
}

std::ostream & operator<<(std::ostream & out, contour_data_t const & vertices)
{
    for (size_t i = 0; i != vertices.size(); ++i)
    {
        out << vertices[i] << " ";
    }
    out << std::endl;
    return out;
}

point_t const & leftmost::operator() (point_t const & l, point_t const & r) const
{
    if(l.x < r.x)
    {
        return l;
    }
    return r;
}

point_t const & rightmost::operator() (point_t const & l, point_t const & r) const
{
    if(l.x > r.x)
    {
        return l;
    }
    return r;
}

